package editorsvg.css;

import editorsvg.editor.documento.ElementoEditable;
import editorsvg.editor.documento.ElementoEstilo;
import editorsvg.editor.documento.ElementoSVG;
import editorsvg.editor.documento.Visitante;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.w3c.css.sac.AttributeCondition;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.Condition;
import org.w3c.css.sac.ConditionalSelector;
import org.w3c.css.sac.DescendantSelector;
import org.w3c.css.sac.DocumentHandler;
import org.w3c.css.sac.ElementSelector;
import org.w3c.css.sac.InputSource;
import org.w3c.css.sac.LexicalUnit;
import org.w3c.css.sac.SACMediaList;
import org.w3c.css.sac.Selector;
import org.w3c.css.sac.SelectorList;
import org.w3c.css.sac.SiblingSelector;
import org.w3c.flute.parser.Parser;

/**
 * Interpreta las reglas CSS de un documento.
 * @author Jorge Berjano
 */
public class InterpreteCss {

    private List<Regla> reglas = new ArrayList<Regla>();
    private List<Regla> reglasSelectorActual;
    private List<Asignacion> asignacionesSelectorActual;
    private Parser parser;
    private List<String> errores = new ArrayList<String>();

    public InterpreteCss() {
        parser = new Parser();
        parser.setDocumentHandler(new MiHandler());
    }

    /**
     * Interpreta las reglas sobre un elemento editable (normalmente un documento).
     * Se hacen dos pasadas, en la primera se ejecutan todas las asignaciones,
     * en la segunda, solo las importantes.
     * @param raiz
     */
    public void interpretar(ElementoEditable raiz) {
        crearReglas(raiz);

        for (Regla regla : reglas) {
            regla.evaluar(raiz, false);
        }

        for (Regla regla : reglas) {
            regla.evaluar(raiz, true);
        }
    }

    private void crearReglas(ElementoEditable raiz) {
        raiz.recorrer(new Visitante() {

            @Override
            public boolean visitar(ElementoSVG nodoGrafico) {
                if (nodoGrafico instanceof ElementoEstilo) {
                    String texto = ((ElementoEstilo) nodoGrafico).hojaEstilo().getTexto();
                    StringReader reader = new StringReader(texto);
                    try {
                        parser.parseStyleSheet(new InputSource(reader));
                    } catch (Exception ex) {
                        errores.add(ex.getMessage());
                    }
                }
                return true;
            }
        });

        Collections.sort(reglas);
    }

//    public void construirReglas(Reader reader) throws Exception {
//        reglas.clear();
//        parser.parseStyleSheet(new InputSource(reader));
//    }
    public List<Regla> getReglas() {
        return reglas;
    }

    class MiHandler implements DocumentHandler {

        @Override
        public void startDocument(InputSource source) throws CSSException {
        }

        @Override
        public void endDocument(InputSource arg0) throws CSSException {
        }

        @Override
        public void comment(String text) throws CSSException {
        }

        @Override
        public void ignorableAtRule(String atRule) throws CSSException {
        }

        @Override
        public void namespaceDeclaration(String prefix, String uri) throws CSSException {
        }

        @Override
        public void importStyle(String uri, SACMediaList media, String defaultNamespaceURI) throws CSSException {
        }

        @Override
        public void startMedia(SACMediaList media) throws CSSException {
        }

        @Override
        public void endMedia(SACMediaList media) throws CSSException {
        }

        @Override
        public void startPage(String name, String pseudo_page) throws CSSException {
        }

        @Override
        public void endPage(String name, String pseudo_page) throws CSSException {
        }

        @Override
        public void startFontFace() throws CSSException {
        }

        @Override
        public void endFontFace() throws CSSException {
        }

        @Override
        public void startSelector(SelectorList selectors) throws CSSException {
            reglasSelectorActual = new ArrayList<Regla>();

            System.out.println("nuevo selector");
            for (int i = 0; i < selectors.getLength(); i++) {
                Regla regla = new Regla();
                
                Condicion condicion = interpretarSelector(selectors.item(i));
                regla.setCondicion(condicion);
                reglasSelectorActual.add(regla);
            }
        }        
        

        @Override
        public void endSelector(SelectorList selectors) throws CSSException {

            for (Regla regla : reglasSelectorActual) {
                regla.setAsignaciones(asignacionesSelectorActual);
            }
            reglas.addAll(reglasSelectorActual);
            reglasSelectorActual = null;
            asignacionesSelectorActual = null;
//            eslabonActual = null;
        }

        @Override
        public void property(String name, LexicalUnit value, boolean important) throws CSSException {
            if (asignacionesSelectorActual == null) {
                asignacionesSelectorActual = new ArrayList<Asignacion>();
            }
            Asignacion asignacion = new Asignacion(name, value.getStringValue(), important);
            asignacionesSelectorActual.add(asignacion);
        }
    }

    public Condicion interpretarSelector(Selector selector) throws CSSException {
        Condicion condicion = null;
        switch (selector.getSelectorType()) {
            case Selector.SAC_ANY_NODE_SELECTOR:
                System.out.println("selector raiz: " + selector);
                break;
            case Selector.SAC_CONDITIONAL_SELECTOR:
                condicion = interpretarSelectorCondicional((ConditionalSelector) selector);
                break;
            case Selector.SAC_ELEMENT_NODE_SELECTOR:
                condicion = interpretarSelectorTag((ElementSelector) selector);
                break;
            case Selector.SAC_DESCENDANT_SELECTOR:
                condicion = interpretarSelectorDescendiente((DescendantSelector) selector);
                break;
            case Selector.SAC_CHILD_SELECTOR:
                condicion = interpretarSelectorHijo((DescendantSelector) selector);
                break;
            case Selector.SAC_DIRECT_ADJACENT_SELECTOR:
                condicion = interpretarSelectorAdyacente((SiblingSelector) selector);
                break;
        }               
        return condicion;
    }
    
    private Condicion interpretarSelectorAdyacente(SiblingSelector selector) {
        Condicion condicion1 = interpretarSelector(selector.getSelector());
        Condicion condicion2 = interpretarSelector(selector.getSiblingSelector());
        
        return new CondicionAdyacentes(condicion1, condicion2);
    }

    private Condicion interpretarSelectorHijo(DescendantSelector selector) {

        Condicion condicionPadre = interpretarSelector(selector.getAncestorSelector());
        Condicion condicionHijo = interpretarSelector(selector.getSimpleSelector());
        return new CondicionHijoDe(condicionPadre, condicionHijo);
    }

    private Condicion interpretarSelectorDescendiente(DescendantSelector selector) {
        Condicion condicionPadre = interpretarSelector(selector.getAncestorSelector());
        Condicion condicionHijo = interpretarSelector(selector.getSimpleSelector());
        return new CondicionDescendiente(condicionPadre, condicionHijo);
    }

    private Condicion interpretarSelectorTag(ElementSelector selector) {
        return new CondicionTag(selector.getLocalName(), null);
    }

    private Condicion interpretarSelectorCondicional(ConditionalSelector selector) {

        Condicion condicionSiguiente = interpretarSelector(selector.getSimpleSelector());
        
        Condition condition = selector.getCondition();
        Condicion condicion = null;
        switch (condition.getConditionType()) {
            case Condition.SAC_CLASS_CONDITION:
                condicion = new CondicionClass(((AttributeCondition) condition).getValue(), condicionSiguiente);
                break;
            case Condition.SAC_ID_CONDITION:
                condicion = new CondicionId(((AttributeCondition) condition).getValue(), condicionSiguiente);
                break;
             case Condition.SAC_ATTRIBUTE_CONDITION:
                condicion = new CondicionAtributo(
                        ((AttributeCondition) condition).getLocalName(),
                        ((AttributeCondition) condition).getValue(),
                        condicionSiguiente);
                break;
            default:
                condicion = new CondicionTodos(condicionSiguiente);
        }
        return condicion;
    }
}
